package ar.edu.itba.it.cg.grupo01.impl.shape;

import static com.google.common.base.Preconditions.checkNotNull;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;

import ar.edu.itba.it.cg.grupo01.api.Intersectable;
import ar.edu.itba.it.cg.grupo01.api.Intersection;
import ar.edu.itba.it.cg.grupo01.api.Ray;
import ar.edu.itba.it.cg.grupo01.api.shape.Shape;
import ar.edu.itba.it.cg.grupo01.impl.IntersectionFactory;

public class AABB implements Intersectable {

	protected IntersectionFactory intersectionFactory;
	private Point3f vmin;
	private Point3f vmax;

	/**
	 * Instantiates an AABB of a given color.
	 * 
	 * @param vmin
	 *            The minimum vertex.
	 * @param vmax
	 *            The maximum vertex.
	 * @param intersectionFactory
	 */
	public AABB(final Point3f vmin, final Point3f vmax,
			IntersectionFactory intersectionFactory) {
		// TODO: check arguments
		this.vmin = vmin;
		this.vmax = vmax;
		this.intersectionFactory = intersectionFactory;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Intersection<? extends Shape> intersects(final Ray ray) {

		checkNotNull(ray, "Ray should not be null");

		Vector3f dir = ray.getDirection();

		Vector3f invDir = new Vector3f(1.0f / dir.x, 1.0f / dir.y, 1.0f / dir.z);
		boolean signDirX = invDir.x < 0;
		boolean signDirY = invDir.y < 0;
		boolean signDirZ = invDir.z < 0;

		Point3f bbox = signDirX ? vmax : vmin;
		float tmin = (bbox.x - ray.getOrigin().x) * invDir.x;
		bbox = signDirX ? vmin : vmax;
		float tmax = (bbox.x - ray.getOrigin().x) * invDir.x;
		bbox = signDirY ? vmax : vmin;
		float tymin = (bbox.y - ray.getOrigin().y) * invDir.y;
		bbox = signDirY ? vmin : vmax;
		float tymax = (bbox.y - ray.getOrigin().y) * invDir.y;

		if ((tmin > tymax) || (tymin > tmax)) {
			return intersectionFactory.emptyIntersection();
		}
		if (tymin > tmin) {
			tmin = tymin;
		}
		if (tymax < tmax) {
			tmax = tymax;
		}

		bbox = signDirZ ? vmax : vmin;
		float tzmin = (bbox.z - ray.getOrigin().z) * invDir.z;
		bbox = signDirZ ? vmin : vmax;
		float tzmax = (bbox.z - ray.getOrigin().z) * invDir.z;

		if ((tmin > tzmax) || (tzmin > tmax)) {
			return intersectionFactory.emptyIntersection();
		}
		if (tzmin > tmin) {
			tmin = tzmin;
		}
		if (tzmax < tmax) {
			tmax = tzmax;
		}

		if (tmin < 0.0f)
			return intersectionFactory.emptyIntersection();

		// intersection points
		List<Point3f> points = new ArrayList<Point3f>();
		points.add(ray.calculatePoint(tmin));
		// TODO: add exit point?

		// TODO as we need a shape we add a dummy one -> ultra bad code
		return (Intersection<? extends Shape>) intersectionFactory
				.createIntersectable(points,
						new DummyShape(intersectionFactory));
	}

	public Set<AABB> split() {

		Set<AABB> splits = new HashSet<AABB>();

		Point3f max1, min1, max2, min2, max3, min3, max4, min4, max5, min5, max6, min6, max7, min7, max8, min8;

		float w = vmax.x - vmin.x;
		float h = vmax.y - vmin.y;
		float d = vmax.z - vmin.z;

		float factor = 2;

		min1 = new Point3f(vmin.x, vmin.y + h / factor, vmin.z);
		max1 = new Point3f(vmin.x + w / factor, vmax.y, vmin.z + d / factor);

		min2 = new Point3f(vmin.x + w / factor, vmin.y + h / factor, vmin.z);
		max2 = new Point3f(vmax.x, vmax.y, vmin.z + d / factor);

		min3 = new Point3f(vmin.x, vmin.y, vmin.z);
		max3 = new Point3f(vmin.x + w / factor, vmin.y + h / factor, vmin.z + d
				/ factor);

		min4 = new Point3f(vmin.x + w / factor, vmin.y, vmin.z);
		max4 = new Point3f(vmax.x, vmin.y + h / factor, vmin.z + d / factor);

		min5 = new Point3f(vmin.x, vmin.y + h / factor, vmin.z + d / factor);
		max5 = new Point3f(vmin.x + w / factor, vmax.y, vmax.z);

		min6 = new Point3f(vmin.x + w / factor, vmin.y + h / factor, vmin.z + d
				/ factor);
		max6 = new Point3f(vmax.x, vmax.y, vmax.z);

		min7 = new Point3f(vmin.x + w / factor, vmin.y, vmin.z + d / factor);
		max7 = new Point3f(vmax.x, vmin.y + h / factor, vmax.z);

		min8 = new Point3f(vmin.x, vmin.y, vmin.z + d / factor);
		max8 = new Point3f(vmin.x + w / factor, vmin.y + h / factor, vmax.z);

		splits.add(new AABB(min1, max1, intersectionFactory));
		splits.add(new AABB(min2, max2, intersectionFactory));
		splits.add(new AABB(min3, max3, intersectionFactory));
		splits.add(new AABB(min4, max4, intersectionFactory));
		splits.add(new AABB(min5, max5, intersectionFactory));
		splits.add(new AABB(min6, max6, intersectionFactory));
		splits.add(new AABB(min7, max7, intersectionFactory));
		splits.add(new AABB(min8, max8, intersectionFactory));

		return splits;
	}

	public Point3f getVmin() {
		return (Point3f) vmin.clone();
	}

	public Point3f getVmax() {
		return (Point3f) vmax.clone();
	}

	public Point3f getMid() {
		return mid(vmax, vmin);
	}

	public Vector3f getExtent() {

		Vector3f extent = new Vector3f(vmax.x, vmax.y, vmax.z);
		extent.sub(vmin);
		extent.scale(0.5f);

		return extent;
	}
	
	public final boolean intersects(AABB b) {
        return ((b != null) && (vmin.x <= b.vmax.x) && (vmax.x >= b.vmin.x) && (vmin.y <= b.vmax.y) && (vmax.y >= b.vmin.y) && (vmin.z <= b.vmax.z) && (vmax.z >= b.vmin.z));
    }

	public static final Point3f mid(Point3f p1, Point3f p2) {

		float x = 0.5f * (p1.x + p2.x);
		float y = 0.5f * (p1.y + p2.y);
		float z = 0.5f * (p1.z + p2.z);

		return new Point3f(x, y, z);
	}

	public boolean contains(Point3f p) {
		return (p.x >= vmin.x) && (p.x <= vmax.x) && (p.y >= vmin.y)
				&& (p.y <= vmax.y) && (p.z >= vmin.z) && (p.z <= vmax.z);
	}

}
